
#define LOG_TAG "SurfaceTexture_test"
//#define LOG_NDEBUG 0
#if 0
#include <gui/SurfaceTexture.h>
#include <gui/SurfaceTextureClient.h>
#include <ui/GraphicBuffer.h>
#include <utils/String8.h>
#include <utils/threads.h>

#include <gui/ISurfaceComposer.h>
#include <gui/Surface.h>
#include <gui/SurfaceComposerClient.h>

#include <EGL/egl.h>
#include <EGL/eglext.h>
#include <GLES2/gl2.h> 
#include <GLES2/gl2ext.h>

#include <ui/FramebufferNativeWindow.h>

using namespace android;

#define ASSERT_EQ(a, b)  b
#define EXPECT_TRUE(a)   a
#define ASSERT_NE(a, b)  b
#define ASSERT_TRUE(a)   a
#define FAIL()  exit(-1)
#define RecordProperty(a,b)
#define SCOPED_TRACE(a) printf(a)
#define ASSERT_NO_FATAL_FAILURE(a) a

class GLTest {
public:

    GLTest():
            mEglDisplay(EGL_NO_DISPLAY),
            mEglSurface(EGL_NO_SURFACE),
            mEglContext(EGL_NO_CONTEXT) {
    }

    virtual void SetUp() {
        mEglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
        ASSERT_EQ(EGL_SUCCESS, eglGetError());
        ASSERT_NE(EGL_NO_DISPLAY, mEglDisplay);

        EGLint majorVersion;
        EGLint minorVersion;
        EXPECT_TRUE(eglInitialize(mEglDisplay, &majorVersion, &minorVersion));
        ASSERT_EQ(EGL_SUCCESS, eglGetError());
        RecordProperty("EglVersionMajor", majorVersion);
        RecordProperty("EglVersionMajor", minorVersion);

        EGLint numConfigs = 0;
        EXPECT_TRUE(eglChooseConfig(mEglDisplay, getConfigAttribs(), &mGlConfig,
                1, &numConfigs));
        ASSERT_EQ(EGL_SUCCESS, eglGetError());

        char* displaySecsEnv = getenv("GLTEST_DISPLAY_SECS");
        if (displaySecsEnv != NULL) {
            mDisplaySecs = atoi(displaySecsEnv);
            if (mDisplaySecs < 0) {
                mDisplaySecs = 0;
            }
        } else {
            mDisplaySecs = 0;
        }

        if (mDisplaySecs > 0) {
            mComposerClient = new SurfaceComposerClient;
            ASSERT_EQ(NO_ERROR, mComposerClient->initCheck());

            mSurfaceControl = mComposerClient->createSurface(
                    String8("Test Surface"),
                    getSurfaceWidth(), getSurfaceHeight(),
                    PIXEL_FORMAT_RGB_888, 0);

            ASSERT_TRUE(mSurfaceControl != NULL);
            ASSERT_TRUE(mSurfaceControl->isValid());

            SurfaceComposerClient::openGlobalTransaction();
            ASSERT_EQ(NO_ERROR, mSurfaceControl->setLayer(0x7FFFFFFF));
            ASSERT_EQ(NO_ERROR, mSurfaceControl->show());
            SurfaceComposerClient::closeGlobalTransaction();

            sp<ANativeWindow> window = mSurfaceControl->getSurface();
            mEglSurface = eglCreateWindowSurface(mEglDisplay, mGlConfig,
                    window.get(), NULL);
        } else {
            EGLint pbufferAttribs[] = {
                EGL_WIDTH, getSurfaceWidth(),
                EGL_HEIGHT, getSurfaceHeight(),
                EGL_NONE };

            mEglSurface = eglCreatePbufferSurface(mEglDisplay, mGlConfig,
                    pbufferAttribs);
        }
        ASSERT_EQ(EGL_SUCCESS, eglGetError());
        ASSERT_NE(EGL_NO_SURFACE, mEglSurface);

        mEglContext = eglCreateContext(mEglDisplay, mGlConfig, EGL_NO_CONTEXT,
                getContextAttribs());
        ASSERT_EQ(EGL_SUCCESS, eglGetError());
        ASSERT_NE(EGL_NO_CONTEXT, mEglContext);

        EXPECT_TRUE(eglMakeCurrent(mEglDisplay, mEglSurface, mEglSurface,
                mEglContext));
        ASSERT_EQ(EGL_SUCCESS, eglGetError());

        EGLint w, h;
        EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_WIDTH, &w));
        ASSERT_EQ(EGL_SUCCESS, eglGetError());
        EXPECT_TRUE(eglQuerySurface(mEglDisplay, mEglSurface, EGL_HEIGHT, &h));
        ASSERT_EQ(EGL_SUCCESS, eglGetError());
        RecordProperty("EglSurfaceWidth", w);
        RecordProperty("EglSurfaceHeight", h);

        glViewport(0, 0, w, h);
        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
    }

    virtual void TearDown() {
        // Display the result
        if (mDisplaySecs > 0 && mEglSurface != EGL_NO_SURFACE) {
            eglSwapBuffers(mEglDisplay, mEglSurface);
            sleep(mDisplaySecs);
        }

        if (mComposerClient != NULL) {
            mComposerClient->dispose();
        }
        if (mEglContext != EGL_NO_CONTEXT) {
            eglDestroyContext(mEglDisplay, mEglContext);
        }
        if (mEglSurface != EGL_NO_SURFACE) {
            eglDestroySurface(mEglDisplay, mEglSurface);
        }
        if (mEglDisplay != EGL_NO_DISPLAY) {
            eglMakeCurrent(mEglDisplay, EGL_NO_SURFACE, EGL_NO_SURFACE,
                    EGL_NO_CONTEXT);
            eglTerminate(mEglDisplay);
        }
        ASSERT_EQ(EGL_SUCCESS, eglGetError());
    }

    virtual EGLint const* getConfigAttribs() {
        static EGLint sDefaultConfigAttribs[] = {
            EGL_SURFACE_TYPE, EGL_PBUFFER_BIT,
            EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
            EGL_RED_SIZE, 8,
            EGL_GREEN_SIZE, 8,
            EGL_BLUE_SIZE, 8,
            EGL_ALPHA_SIZE, 8,
            EGL_DEPTH_SIZE, 16,
            EGL_STENCIL_SIZE, 8,
            EGL_NONE };

        return sDefaultConfigAttribs;
    }

    virtual EGLint const* getContextAttribs() {
        static EGLint sDefaultContextAttribs[] = {
            EGL_CONTEXT_CLIENT_VERSION, 2,
            EGL_NONE };

        return sDefaultContextAttribs;
    }

    virtual EGLint getSurfaceWidth() {
        return 512;
    }

    virtual EGLint getSurfaceHeight() {
        return 512;
    }

    int mDisplaySecs;
    sp<SurfaceComposerClient> mComposerClient;
    sp<SurfaceControl> mSurfaceControl;

    EGLDisplay mEglDisplay;
    EGLSurface mEglSurface;
    EGLContext mEglContext;
    EGLConfig  mGlConfig;
};

static void loadShader(GLenum shaderType, const char* pSource,
        GLuint* outShader) {
    GLuint shader = glCreateShader(shaderType);
    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
    if (shader) {
        glShaderSource(shader, 1, &pSource, NULL);
        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
        glCompileShader(shader);
        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
        GLint compiled = 0;
        glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled);
        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
        if (!compiled) {
            GLint infoLen = 0;
            glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            if (infoLen) {
                char* buf = (char*) malloc(infoLen);
                if (buf) {
                    glGetShaderInfoLog(shader, infoLen, NULL, buf);
                    printf("Shader compile log:\n%s\n", buf);
                    free(buf);
                    FAIL();
                }
            } else {
                char* buf = (char*) malloc(0x1000);
                if (buf) {
                    glGetShaderInfoLog(shader, 0x1000, NULL, buf);
                    printf("Shader compile log:\n%s\n", buf);
                    free(buf);
                    FAIL();
                }
            }
            glDeleteShader(shader);
            shader = 0;
        }
    }
    ASSERT_TRUE(shader != 0);
    *outShader = shader;
}

static void createProgram(const char* pVertexSource,
        const char* pFragmentSource, GLuint* outPgm) {
    GLuint vertexShader, fragmentShader;
    {
        SCOPED_TRACE("compiling vertex shader");
        ASSERT_NO_FATAL_FAILURE(loadShader(GL_VERTEX_SHADER, pVertexSource,
                &vertexShader));
    }
    {
        SCOPED_TRACE("compiling fragment shader");
        ASSERT_NO_FATAL_FAILURE(loadShader(GL_FRAGMENT_SHADER, pFragmentSource,
                &fragmentShader));
    }

    GLuint program = glCreateProgram();
    ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
    if (program) {
        glAttachShader(program, vertexShader);
        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
        glAttachShader(program, fragmentShader);
        ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
        glLinkProgram(program);
        GLint linkStatus = GL_FALSE;
        glGetProgramiv(program, GL_LINK_STATUS, &linkStatus);
        if (linkStatus != GL_TRUE) {
            GLint bufLength = 0;
            glGetProgramiv(program, GL_INFO_LOG_LENGTH, &bufLength);
            if (bufLength) {
                char* buf = (char*) malloc(bufLength);
                if (buf) {
                    glGetProgramInfoLog(program, bufLength, NULL, buf);
                    printf("Program link log:\n%s\n", buf);
                    free(buf);
                    FAIL();
                }
            }
            glDeleteProgram(program);
            program = 0;
        }
    }
    glDeleteShader(vertexShader);
    glDeleteShader(fragmentShader);
    ASSERT_TRUE(program != 0);
    *outPgm = program;
}

class SurfaceTextureGLTest : public GLTest {
public:
    enum { TEX_ID = 123 };

    virtual void SetUp() {
        GLTest::SetUp();
        mST = new SurfaceTexture(TEX_ID);
        mSTC = new SurfaceTextureClient(mST);
        mANW = mSTC;
        mTextureRenderer = new TextureRenderer(TEX_ID, mST);
        ASSERT_NO_FATAL_FAILURE(mTextureRenderer->SetUp());
        mFW = new FrameWaiter;
        mST->setFrameAvailableListener(mFW);
    }

    virtual void TearDown() {
        mANW.clear();
        mSTC.clear();
        mST.clear();
        GLTest::TearDown();
    }

    void drawTexture() {
        mTextureRenderer->drawTexture();
    }

void TexturingFromCpuFilledRGBABufferNpot();

    class TextureRenderer: public RefBase {
    public:
        TextureRenderer(GLuint texName, const sp<SurfaceTexture>& st):
                mTexName(texName),
                mST(st) {
        }
        void SetUp() {
            const char vsrc[] =
                "attribute vec4 vPosition;\n"
                "varying vec2 texCoords;\n"
                "uniform mat4 texMatrix;\n"
                "void main() {\n"
                "  vec2 vTexCoords = 0.5 * (vPosition.xy + vec2(1.0, 1.0));\n"
                "  texCoords = (texMatrix * vec4(vTexCoords, 0.0, 1.0)).xy;\n"
                "  gl_Position = vPosition;\n"
                "}\n";

            const char fsrc[] =
                "#extension GL_OES_EGL_image_external : require\n"
                "precision mediump float;\n"
                "uniform samplerExternalOES texSampler;\n"
                "varying vec2 texCoords;\n"
                "void main() {\n"
                "  gl_FragColor = texture2D(texSampler, texCoords);\n"
                "}\n";

            {
                SCOPED_TRACE("creating shader program");
                ASSERT_NO_FATAL_FAILURE(createProgram(vsrc, fsrc, &mPgm));
            }

            mPositionHandle = glGetAttribLocation(mPgm, "vPosition");
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            ASSERT_NE(-1, mPositionHandle);
            mTexSamplerHandle = glGetUniformLocation(mPgm, "texSampler");
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            ASSERT_NE(-1, mTexSamplerHandle);
            mTexMatrixHandle = glGetUniformLocation(mPgm, "texMatrix");
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            ASSERT_NE(-1, mTexMatrixHandle);
        }

        // drawTexture draws the SurfaceTexture over the entire GL viewport.
        void drawTexture() {
            static const GLfloat triangleVertices[] = {
                -1.0f, 1.0f,
                -1.0f, -1.0f,
                1.0f, -1.0f,
                1.0f, 1.0f,
            };

            glVertexAttribPointer(mPositionHandle, 2, GL_FLOAT, GL_FALSE, 0,
                    triangleVertices);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            glEnableVertexAttribArray(mPositionHandle);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());

            glUseProgram(mPgm);
            glUniform1i(mTexSamplerHandle, 0);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            glBindTexture(GL_TEXTURE_EXTERNAL_OES, mTexName);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());

            // XXX: These calls are not needed for GL_TEXTURE_EXTERNAL_OES as
            // they're setting the defautls for that target, but when hacking
            // things to use GL_TEXTURE_2D they are needed to achieve the same
            // behavior.
            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MIN_FILTER,
                    GL_LINEAR);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_MAG_FILTER,
                    GL_LINEAR);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_S,
                    GL_CLAMP_TO_EDGE);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
            glTexParameteri(GL_TEXTURE_EXTERNAL_OES, GL_TEXTURE_WRAP_T,
                    GL_CLAMP_TO_EDGE);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());

            GLfloat texMatrix[16];
            mST->getTransformMatrix(texMatrix);
            glUniformMatrix4fv(mTexMatrixHandle, 1, GL_FALSE, texMatrix);

            glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
            ASSERT_EQ(GLenum(GL_NO_ERROR), glGetError());
        }

        GLuint mTexName;
        sp<SurfaceTexture> mST;
        GLuint mPgm;
        GLint mPositionHandle;
        GLint mTexSamplerHandle;
        GLint mTexMatrixHandle;
    };

    class FrameWaiter : public SurfaceTexture::FrameAvailableListener {
    public:
        FrameWaiter():
                mPendingFrames(0) {
        }

        void waitForFrame() {
            Mutex::Autolock lock(mMutex);
            while (mPendingFrames == 0) {
                mCondition.wait(mMutex);
            }
            mPendingFrames--;
        }

        virtual void onFrameAvailable() {
            Mutex::Autolock lock(mMutex);
            mPendingFrames++;
            mCondition.signal();
        }

        int mPendingFrames;
        Mutex mMutex;
        Condition mCondition;
    };

    // Note that SurfaceTexture will lose the notifications
    // onBuffersReleased and onFrameAvailable as there is currently
    // no way to forward the events.  This DisconnectWaiter will not let the
    // disconnect finish until finishDisconnect() is called.  It will
    // also block until a disconnect is called
    class DisconnectWaiter : public BufferQueue::ConsumerListener {
    public:
        DisconnectWaiter () :
            mWaitForDisconnect(false),
            mPendingFrames(0) {
        }

        void waitForFrame() {
            Mutex::Autolock lock(mMutex);
            while (mPendingFrames == 0) {
                mFrameCondition.wait(mMutex);
            }
            mPendingFrames--;
        }

        virtual void onFrameAvailable() {
            Mutex::Autolock lock(mMutex);
            mPendingFrames++;
            mFrameCondition.signal();
        }

        virtual void onBuffersReleased() {
            Mutex::Autolock lock(mMutex);
            while (!mWaitForDisconnect) {
                mDisconnectCondition.wait(mMutex);
            }
        }

        void finishDisconnect() {
            Mutex::Autolock lock(mMutex);
            mWaitForDisconnect = true;
            mDisconnectCondition.signal();
        }

    private:
        Mutex mMutex;

        bool mWaitForDisconnect;
        Condition mDisconnectCondition;

        int mPendingFrames;
        Condition mFrameCondition;
    };

    sp<SurfaceTexture> mST;
    sp<SurfaceTextureClient> mSTC;
    sp<ANativeWindow> mANW;
    sp<TextureRenderer> mTextureRenderer;
    sp<FrameWaiter> mFW;
};



void fillRGBA8Buffer(uint8_t* buf, int w, int h, int stride) {
    const size_t PIXEL_SIZE = 4;
    for (int x = 0; x < w; x++) {
        for (int y = 0; y < h; y++) {
            off_t offset = (y * stride + x) * PIXEL_SIZE;
            for (int c = 0; c < 4; c++) {
                int parityX = (x / (1 << (c+2))) & 1;
                int parityY = (y / (1 << (c+2))) & 1;
                buf[offset + c] = (parityX ^ parityY) ? 231 : 35;
            }
        }
    }
}



// Produce a single RGBA8 frame by filling a buffer with a checkerboard pattern
// using the CPU.  This assumes that the ANativeWindow is already configured to
// allow this to be done (e.g. the format is set to RGBA8).
//
// Calls to this function should be wrapped in an ASSERT_NO_FATAL_FAILURE().
void produceOneRGBA8Frame(const sp<ANativeWindow>& anw) {
    android_native_buffer_t* anb;
    ASSERT_EQ(NO_ERROR, native_window_dequeue_buffer_and_wait(anw.get(),
            &anb));
    ASSERT_TRUE(anb != NULL);

    sp<GraphicBuffer> buf(new GraphicBuffer(anb, false));

    uint8_t* img = NULL;
    ASSERT_EQ(NO_ERROR, buf->lock(GRALLOC_USAGE_SW_WRITE_OFTEN,
            (void**)(&img)));
    fillRGBA8Buffer(img, buf->getWidth(), buf->getHeight(), buf->getStride());
    ASSERT_EQ(NO_ERROR, buf->unlock());
    ASSERT_EQ(NO_ERROR, anw->queueBuffer(anw.get(), buf->getNativeBuffer(),
            -1));
}


void SurfaceTextureGLTest::TexturingFromCpuFilledRGBABufferNpot() {
    const int texWidth = 64;
    const int texHeight = 66;

    ASSERT_EQ(NO_ERROR, native_window_set_buffers_geometry(mANW.get(),
            texWidth, texHeight, HAL_PIXEL_FORMAT_RGBA_8888));
    ASSERT_EQ(NO_ERROR, native_window_set_usage(mANW.get(),
            GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN));

    ASSERT_NO_FATAL_FAILURE(produceOneRGBA8Frame(mANW));

    ASSERT_EQ(NO_ERROR, mST->updateTexImage());

    glClearColor(0.2, 0.2, 0.2, 0.2);
    glClear(GL_COLOR_BUFFER_BIT);

    glViewport(0, 0, texWidth, texHeight);
    drawTexture();
}

int main(){
    SurfaceTextureGLTest test;
    test.SetUp();
    test.TexturingFromCpuFilledRGBABufferNpot();
}
#endif
int main(){}